<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>3.Zone区域之zone.js | 被删的前端游乐场</title>
    <meta name="generator" content="VuePress 1.8.2">
    
    <meta name="description" content="Just playing around">
    
    <link rel="preload" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css" as="style"><link rel="preload" href="/front-end-playground/assets/js/app.1e2670bf.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/2.38d016d1.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/3.e3f029cb.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/113.a2fd3964.js" as="script">
    <link rel="stylesheet" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/front-end-playground/" class="home-link router-link-active"><!----> <span class="site-name">被删的前端游乐场</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>Angular框架解读</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-0-prestart.html" class="sidebar-link">0.预热篇</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-metadata.html" class="sidebar-link">1.元数据和装饰器</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-dom-define.html" class="sidebar-link">2.视图抽象定义</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html" aria-current="page" class="active sidebar-link">3.Zone区域之zone.js</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html#zone-js" class="sidebar-link">zone.js</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html#总结" class="sidebar-link">总结</a></li></ul></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html" class="sidebar-link">4.Zone区域之ngZone</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html" class="sidebar-link">5.模块化组织</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-1-basic-concepts.html" class="sidebar-link">6.依赖注入的基本概念</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html" class="sidebar-link">7.多级依赖注入设计</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-3-bootstrap.html" class="sidebar-link">8.依赖注入的引导过程</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-0-design.html" class="sidebar-link">9.Ivy编译器整体设计</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-1-view-data-and-node-injector.html" class="sidebar-link">10.Ivy编译器的视图数据和依赖解析</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>玩转 Angular</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Cycle.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>D3.js 和 Tree</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Three.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>BOX2D.js 文档与教程</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><p>作为“为大型前端项目”而设计的前端框架，Angular 其实有许多值得参考和学习的设计，本系列主要用于研究这些设计和功能的实现原理。本文主要围绕 Angular 中的 NgZone 核心能力，这些能力主要基于 zone.js 来实现，因此本文先介绍 zone.js。</p> <p>在 Angular 中，对于数据变更检测使用的是脏检查（dirty check），这曾经在 AngularJS 版本中被诟病，认为存在性能问题。而在 Angular(2+) 版本之后，通过引入模块化组织，以及 NgZone 的设计，提升了脏检查的性能。</p> <p>对于 NgZone 的引入，并不只是为了解决脏检查的问题，它解决了很多 Javascript 异步编程时上下文的问题，其中 zone.js 便是针对异步编程提出的作用域解决方案。</p> <h2 id="zone-js"><a href="#zone-js" class="header-anchor">#</a> zone.js</h2> <p>Zone 是跨异步任务而持久存在的执行上下文，zone.js 提供以下能力：</p> <ul><li>提供异步操作之间的执行上下文</li> <li>提供异步生命周期挂钩</li> <li>提供统一的异步错误处理机制</li></ul> <h3 id="异步操作的困惑"><a href="#异步操作的困惑" class="header-anchor">#</a> 异步操作的困惑</h3> <p>在 Javascript 中，<a href="https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf#6e11" target="_blank" rel="noopener noreferrer">代码执行过程中会产生堆栈，函数会在堆栈中执行<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。</p> <p>对于异步操作来说，异步代码和函数执行的时候，上下文可能发生了变化，为此可能导致一些难题。比如：</p> <ul><li>异步代码执行时，上下文发生了变更，导致预期不一致</li> <li><code>throw Error</code>时，无法准确定位到上下文</li> <li>测试某个函数的执行耗时，但因为函数内有异步逻辑，无法得到准确的执行时间</li></ul> <p>一般来说，异步代码执行时的上下文问题，可以通过传参或是全局变量的方式来解决，但两种方式都不是很优雅（尤其全局变量）。zone.js 正是为了解决以上问题而提出的，我们来看看。</p> <h3 id="zone-js-的设计"><a href="#zone-js-的设计" class="header-anchor">#</a> zone.js 的设计</h3> <p>zone.js 的设计灵感来自 <a href="https://dart.dev/articles/archive/zones" target="_blank" rel="noopener noreferrer">Dart Zones<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>，你也可以将其视为 JavaScript VM 中的 <a href="https://en.wikipedia.org/wiki/Thread-local_storage" target="_blank" rel="noopener noreferrer">TLS--线程本地存储<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。</p> <p>zone 具有当前区域的概念：当前区域是随所有异步操作一起传播的异步上下文，它表示与当前正在执行的堆栈帧/异步任务关联的区域。</p> <p>当前上下文可以使用<code>Zone.current</code>获取，可比作 Javascript 中的<code>this</code>，在 zone.js 中使用<code>_currentZoneFrame</code>变量跟踪当前区域。每个区域都有<code>name</code>属性，主要用于工具和调试目的，zone.js 还定义了用于操纵区域的方法：</p> <ul><li><code>zone.fork(zoneSpec)</code>: 创建一个新的子区域，并将其<code>parent</code>设置为用于分支的区域</li> <li><code>zone.run(callback, ...)</code>：在给定区域中同步调用一个函数</li> <li><code>zone.runGuarded(callback, ...)</code>：与<code>run</code>捕获运行时错误相同，并提供了一种拦截它们的机制。如果任何父区域未处理错误，则将其重新抛出。</li> <li><code>zone.wrap(callback)</code>：产生一个新的函数，该函数将区域绑定在一个闭包中，并在执行<code>zone.runGuarded(callback)</code>时执行，与 JavaScript 中的<code>Function.prototype.bind</code>工作原理类似。</li></ul> <p>我们可以看到<code>Zone</code>的主要实现逻辑（<code>new Zone()</code>/<code>fork()</code>/<code>run()</code>）也相对简单：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">class</span> <span class="token class-name">Zone</span> <span class="token keyword">implements</span> <span class="token class-name">AmbientZone</span> <span class="token punctuation">{</span>
  <span class="token comment">// 获取根区域</span>
  <span class="token keyword">static</span> <span class="token keyword">get</span> <span class="token function">root</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> AmbientZone <span class="token punctuation">{</span>
    <span class="token keyword">let</span> zone <span class="token operator">=</span> Zone<span class="token punctuation">.</span>current<span class="token punctuation">;</span>
    <span class="token comment">// 找到最外层，父区域为自己</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>zone<span class="token punctuation">.</span>parent<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      zone <span class="token operator">=</span> zone<span class="token punctuation">.</span>parent<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> zone<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 获取当前区域</span>
  <span class="token keyword">static</span> <span class="token keyword">get</span> <span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> AmbientZone <span class="token punctuation">{</span>
    <span class="token keyword">return</span> _currentZoneFrame<span class="token punctuation">.</span>zone<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">private</span> _parent<span class="token operator">:</span> Zone<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// 父区域</span>
  <span class="token keyword">private</span> _name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token comment">// 区域名字</span>
  <span class="token keyword">private</span> _properties<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">[</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">]</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token comment">// 拦截区域操作时的委托，用于生命周期钩子相关处理</span>
  <span class="token keyword">private</span> _zoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">;</span>

  <span class="token function">constructor</span><span class="token punctuation">(</span>parent<span class="token operator">:</span> Zone<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">,</span> zoneSpec<span class="token operator">:</span> ZoneSpec<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 创建区域时，设置区域的属性</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_parent <span class="token operator">=</span> parent<span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_name <span class="token operator">=</span> zoneSpec <span class="token operator">?</span> zoneSpec<span class="token punctuation">.</span>name <span class="token operator">||</span> <span class="token string">'unnamed'</span> <span class="token operator">:</span> <span class="token string">'&lt;root&gt;'</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_properties <span class="token operator">=</span> zoneSpec <span class="token operator">&amp;&amp;</span> zoneSpec<span class="token punctuation">.</span>properties <span class="token operator">||</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_zoneDelegate <span class="token operator">=</span>
        <span class="token keyword">new</span> <span class="token class-name">ZoneDelegate</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_parent <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_parent<span class="token punctuation">.</span>_zoneDelegate<span class="token punctuation">,</span> zoneSpec<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// fork 会产生子区域</span>
  <span class="token keyword">public</span> <span class="token function">fork</span><span class="token punctuation">(</span>zoneSpec<span class="token operator">:</span> ZoneSpec<span class="token punctuation">)</span><span class="token operator">:</span> AmbientZone <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>zoneSpec<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'ZoneSpec required!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 以当前区域为父区域，调用 new Zone() 产生子区域</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_zoneDelegate<span class="token punctuation">.</span><span class="token function">fork</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> zoneSpec<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 在区域中同步运行某段代码</span>
  <span class="token keyword">public</span> <span class="token function">run</span><span class="token punctuation">(</span>callback<span class="token operator">:</span> <span class="token builtin">Function</span><span class="token punctuation">,</span> applyThis<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> source<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">;</span>
  <span class="token keyword">public</span> <span class="token generic-function"><span class="token function">run</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>
      <span class="token function-variable function">callback</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">,</span> applyThis<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> source<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">{</span>
    <span class="token comment">// 准备执行，入栈处理</span>
    _currentZoneFrame <span class="token operator">=</span> <span class="token punctuation">{</span>parent<span class="token operator">:</span> _currentZoneFrame<span class="token punctuation">,</span> zone<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token comment">// 使用 callback.apply(applyThis, applyArgs) 实现</span>
      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_zoneDelegate<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> callback<span class="token punctuation">,</span> applyThis<span class="token punctuation">,</span> applyArgs<span class="token punctuation">,</span> source<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
      <span class="token comment">// 执行完毕，出栈处理</span>
      _currentZoneFrame <span class="token operator">=</span> _currentZoneFrame<span class="token punctuation">.</span>parent<span class="token operator">!</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token operator">...</span>
<span class="token punctuation">}</span>
</code></pre></div><p>除了上面介绍的，Zone 还提供了许多方法来运行、计划和取消任务，包括：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">interface</span> <span class="token class-name">Zone</span> <span class="token punctuation">{</span>
  <span class="token operator">...</span>
  <span class="token comment">// 通过在任务区域中恢复 Zone.currentTask 来执行任务</span>
  <span class="token generic-function"><span class="token function">runTask</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>task<span class="token operator">:</span> Task<span class="token punctuation">,</span> applyThis<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span>
  <span class="token comment">// 安排一个 MicroTask</span>
  <span class="token function">scheduleMicroTask</span><span class="token punctuation">(</span>source<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> callback<span class="token operator">:</span> <span class="token builtin">Function</span><span class="token punctuation">,</span> data<span class="token operator">?</span><span class="token operator">:</span> TaskData<span class="token punctuation">,</span> customSchedule<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>task<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">)</span><span class="token operator">:</span> MicroTask<span class="token punctuation">;</span>
  <span class="token comment">// 安排一个 MacroTask</span>
  <span class="token function">scheduleMacroTask</span><span class="token punctuation">(</span>source<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> callback<span class="token operator">:</span> <span class="token builtin">Function</span><span class="token punctuation">,</span> data<span class="token operator">?</span><span class="token operator">:</span> TaskData<span class="token punctuation">,</span> customSchedule<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>task<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">,</span> customCancel<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>task<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">)</span><span class="token operator">:</span> MacroTask<span class="token punctuation">;</span>
  <span class="token comment">// 安排一个 EventTask</span>
  <span class="token function">scheduleEventTask</span><span class="token punctuation">(</span>source<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> callback<span class="token operator">:</span> <span class="token builtin">Function</span><span class="token punctuation">,</span> data<span class="token operator">?</span><span class="token operator">:</span> TaskData<span class="token punctuation">,</span> customSchedule<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>task<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">,</span> customCancel<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>task<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">)</span><span class="token operator">:</span> EventTask<span class="token punctuation">;</span>
  <span class="token comment">// 安排现有任务（对重新安排已取消的任务很有用）</span>
  <span class="token generic-function"><span class="token function">scheduleTask</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> Task<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>task<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span>
  <span class="token comment">// 允许区域拦截计划任务的取消，使用 ZoneSpec.onCancelTask​​ 配置拦截</span>
  <span class="token function">cancelTask</span><span class="token punctuation">(</span>task<span class="token operator">:</span> Task<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><h3 id="让异步逻辑运行在指定区域中"><a href="#让异步逻辑运行在指定区域中" class="header-anchor">#</a> 让异步逻辑运行在指定区域中</h3> <p>在 zone.js 中，通过<code>zone.fork</code>可以创建子区域，通过<code>zone.run</code>可让函数（包括函数里的异步逻辑）在指定的区域中运行。举个例子：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">const</span> zoneBC <span class="token operator">=</span> Zone<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">fork</span><span class="token punctuation">(</span><span class="token punctuation">{</span>name<span class="token operator">:</span> <span class="token string">'BC'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">c</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Zone<span class="token punctuation">.</span>current<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// BC</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Zone<span class="token punctuation">.</span>current<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// BC</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Zone<span class="token punctuation">.</span>current<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// &lt;root&gt;</span>
    zoneBC<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>执行的效果如图：
<img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/angular-zone-1.png" alt=""></p> <p>实际上，每个异步任务的调用堆栈会以根区域开始。因此，在 zone.js 中该区域会使用与任务关联的信息来还原正确的区域，然后调用该任务：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/angular-zone-2.png" alt=""></p> <p>对于<code>Zone.fork()</code>和<code>Zone.run()</code>的作用和实现，上面已经介绍过了。那么，zone.js 是如何识别出异步任务的呢？其实 zone.js 主要是通过猴子补丁拦截异步 API（包括 DOM 事件、<code>XMLHttpRequest</code>和 NodeJS 的 API 如<code>EventEmitter</code>、<code>fs</code>等）来实现这些功能：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token comment">// 为指定的本地模块加载补丁</span>
<span class="token keyword">static</span> <span class="token function">__load_patch</span><span class="token punctuation">(</span>name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> fn<span class="token operator">:</span> _PatchFn<span class="token punctuation">,</span> ignoreDuplicate <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
  <span class="token comment">// 检查是否已经加载补丁</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>patches<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ignoreDuplicate <span class="token operator">&amp;&amp;</span> checkDuplicate<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'Already loaded patch: '</span> <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token comment">// 检查是否需要加载补丁</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>global<span class="token punctuation">[</span><span class="token string">'__Zone_disable_'</span> <span class="token operator">+</span> name<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> perfName <span class="token operator">=</span> <span class="token string">'Zone:'</span> <span class="token operator">+</span> name<span class="token punctuation">;</span>
    <span class="token comment">// 使用 performance.mark 标记时间戳</span>
    <span class="token function">mark</span><span class="token punctuation">(</span>perfName<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 拦截指定异步 API，并进行相关处理</span>
    patches<span class="token punctuation">[</span>name<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">fn</span><span class="token punctuation">(</span>global<span class="token punctuation">,</span> Zone<span class="token punctuation">,</span> _api<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 使用 performance.measure 计算耗时</span>
    <span class="token function">performanceMeasure</span><span class="token punctuation">(</span>perfName<span class="token punctuation">,</span> perfName<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>以<code>setTimeout</code>等定时器为例子，通过拦截和捕获特定 API：</p> <div class="language-ts extra-class"><pre class="language-ts"><code>Zone<span class="token punctuation">.</span><span class="token function">__load_patch</span><span class="token punctuation">(</span><span class="token string">'timers'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>global<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> set <span class="token operator">=</span> <span class="token string">'set'</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> clear <span class="token operator">=</span> <span class="token string">'clear'</span><span class="token punctuation">;</span>
  <span class="token function">patchTimer</span><span class="token punctuation">(</span>global<span class="token punctuation">,</span> set<span class="token punctuation">,</span> clear<span class="token punctuation">,</span> <span class="token string">'Timeout'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">patchTimer</span><span class="token punctuation">(</span>global<span class="token punctuation">,</span> set<span class="token punctuation">,</span> clear<span class="token punctuation">,</span> <span class="token string">'Interval'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">patchTimer</span><span class="token punctuation">(</span>global<span class="token punctuation">,</span> set<span class="token punctuation">,</span> clear<span class="token punctuation">,</span> <span class="token string">'Immediate'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p><code>patchTimer</code>做了很多兼容性的逻辑处理，包括 Node.js 和浏览器环境的检测和处理，其中比较关键的实现逻辑在：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token comment">// 检测该函数属性是否可写</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isPropertyWritable</span><span class="token punctuation">(</span>desc<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> patchDelegate <span class="token operator">=</span> <span class="token function">patchFn</span><span class="token punctuation">(</span>delegate<span class="token operator">!</span><span class="token punctuation">,</span> delegateName<span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 修改函数默认行为</span>
  proto<span class="token punctuation">[</span>name<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">patchDelegate</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments <span class="token keyword">as</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token function">attachOriginToPatched</span><span class="token punctuation">(</span>proto<span class="token punctuation">[</span>name<span class="token punctuation">]</span><span class="token punctuation">,</span> delegate<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>shouldCopySymbolProperties<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">copySymbolProperties</span><span class="token punctuation">(</span>delegate<span class="token punctuation">,</span> proto<span class="token punctuation">[</span>name<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// patchFn 用于使用当前的区域创建 MacroTask 任务</span>
<span class="token keyword">const</span> <span class="token function-variable function">patchFn</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>self<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> args<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> args<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>
    <span class="token keyword">const</span> callback <span class="token operator">=</span> args<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    args<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">timer</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 执行该函数</span>
        <span class="token keyword">return</span> <span class="token function">callback</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        <span class="token comment">// 一些清理工作，比如删除任务的引用等</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token comment">// 使用当前的区域创建 MacroTask 任务，调用 Zone.current.scheduleMacroTask</span>
    <span class="token keyword">const</span> task <span class="token operator">=</span> <span class="token function">scheduleMacroTaskWithCurrentZone</span><span class="token punctuation">(</span>setName<span class="token punctuation">,</span> args<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> options<span class="token punctuation">,</span> scheduleTask<span class="token punctuation">,</span> clearTask<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>task<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> task<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 一些兼容性处理工作，比如对于nodejs 环境，将任务引用保存在 timerId 对象中，用于 clearTimeout</span>
    <span class="token keyword">return</span> task<span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token comment">// 出现异常时，直接返回调用</span>
    <span class="token keyword">return</span> <span class="token function">delegate</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>window<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre></div><p>在这里，计时器相关的 Timer 会被创建 MacroTask 任务并添加到 Zone 的任务中进行处理。在 zone.js 中，有将各种异步任务拆分为三种：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">type</span> <span class="token class-name">TaskType</span> <span class="token operator">=</span> <span class="token string">'microTask'</span><span class="token operator">|</span><span class="token string">'macroTask'</span><span class="token operator">|</span><span class="token string">'eventTask'</span><span class="token punctuation">;</span>
</code></pre></div><p>zone.js 可以支持选择性地打补丁，具体更多的补丁机制可以参考 <a href="https://github.com/angular/angular/blob/master/packages/zone.js/STANDARD-APIS.md" target="_blank" rel="noopener noreferrer">Zone.js's support for standard apis<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。</p> <h3 id="任务执行的生命周期"><a href="#任务执行的生命周期" class="header-anchor">#</a> 任务执行的生命周期</h3> <p>zone.js 提供了异步操作生命周期钩子，有了这些钩子，Zone 可以监视和拦截异步操作的所有生命周期：</p> <ul><li><code>onScheduleTask</code>：此回调将在<code>async</code>操作为之前被调用<code>scheduled</code>，这意味着<code>async</code>操作即将发送到浏览器（或 NodeJS ）以计划在以后运行时</li> <li><code>onInvokeTask</code>：此回调将在真正调用异步回调之前被调用</li> <li><code>onHasTask</code>：当任务队列的状态在<code>empty</code>和之间更改时，将调用此回调<code>not empty</code></li></ul> <p>完整的生命周期钩子包括：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">interface</span> <span class="token class-name">ZoneSpec</span> <span class="token punctuation">{</span>
  <span class="token comment">// 允许拦截 Zone.fork，对该区域进行 fork 时，请求将转发到此方法以进行拦截</span>
  onFork<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> zoneSpec<span class="token operator">:</span> ZoneSpec<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Zone<span class="token punctuation">;</span>
  <span class="token comment">// 允许拦截回调的 wrap</span>
  onIntercept<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> delegate<span class="token operator">:</span> <span class="token builtin">Function</span><span class="token punctuation">,</span> source<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token builtin">Function</span><span class="token punctuation">;</span>
  <span class="token comment">// 允许拦截回调调用</span>
  onInvoke<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> delegate<span class="token operator">:</span> <span class="token builtin">Function</span><span class="token punctuation">,</span> applyThis<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> source<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token builtin">any</span><span class="token punctuation">;</span>
  <span class="token comment">// 允许拦截错误处理</span>
  onHandleError<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> error<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span>
  <span class="token comment">// 允许拦截任务计划</span>
  onScheduleTask<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> task<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Task<span class="token punctuation">;</span>
  <span class="token comment">// 允许拦截任务回调调用</span>
  onInvokeTask<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> task<span class="token operator">:</span> Task<span class="token punctuation">,</span> applyThis<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token builtin">any</span><span class="token punctuation">;</span>
  <span class="token comment">// 允许拦截任务取消</span>
  onCancelTask<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> task<span class="token operator">:</span> Task<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token builtin">any</span><span class="token punctuation">;</span>
  <span class="token comment">// 通知对任务队列为空状态的更改</span>
  onHasTask<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>parentZoneDelegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> currentZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> hasTaskState<span class="token operator">:</span> HasTaskState<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>这些生命周期的钩子回调会在<code>zone.fork()</code>时，通过<code>new Zone()</code>创建子区域并创建和传入到<code>ZoneDelegate</code>中：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">class</span> <span class="token class-name">Zone</span> <span class="token keyword">implements</span> <span class="token class-name">AmbientZone</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>parent<span class="token operator">:</span> Zone<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">,</span> zoneSpec<span class="token operator">:</span> ZoneSpec<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_zoneDelegate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ZoneDelegate</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_parent <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_parent<span class="token punctuation">.</span>_zoneDelegate<span class="token punctuation">,</span> zoneSpec<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>以<code>onFork</code>为例：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">class</span> <span class="token class-name">ZoneDelegate</span> <span class="token keyword">implements</span> <span class="token class-name">AmbientZoneDelegate</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>zone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> parentDelegate<span class="token operator">:</span> ZoneDelegate<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">,</span> zoneSpec<span class="token operator">:</span> ZoneSpec<span class="token operator">|</span><span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>
    <span class="token comment">// 管理 onFork 钩子回调</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_forkZS <span class="token operator">=</span> zoneSpec <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>zoneSpec <span class="token operator">&amp;&amp;</span> zoneSpec<span class="token punctuation">.</span>onFork <span class="token operator">?</span> zoneSpec <span class="token operator">:</span> parentDelegate<span class="token operator">!</span><span class="token punctuation">.</span>_forkZS<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_forkDlgt <span class="token operator">=</span> zoneSpec <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>zoneSpec<span class="token punctuation">.</span>onFork <span class="token operator">?</span> parentDelegate <span class="token operator">:</span> parentDelegate<span class="token operator">!</span><span class="token punctuation">.</span>_forkDlgt<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_forkCurrZone <span class="token operator">=</span>
        zoneSpec <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>zoneSpec<span class="token punctuation">.</span>onFork <span class="token operator">?</span> <span class="token keyword">this</span><span class="token punctuation">.</span>zone <span class="token operator">:</span> parentDelegate<span class="token operator">!</span><span class="token punctuation">.</span>_forkCurrZone<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// fork 调用时，会检查是否有 onFork 钩子回调注册，并进行调用</span>
  <span class="token function">fork</span><span class="token punctuation">(</span>targetZone<span class="token operator">:</span> Zone<span class="token punctuation">,</span> zoneSpec<span class="token operator">:</span> ZoneSpec<span class="token punctuation">)</span><span class="token operator">:</span> AmbientZone <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_forkZS <span class="token operator">?</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_forkZS<span class="token punctuation">.</span>onFork<span class="token operator">!</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_forkDlgt<span class="token operator">!</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>zone<span class="token punctuation">,</span> targetZone<span class="token punctuation">,</span> zoneSpec<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Zone</span><span class="token punctuation">(</span>targetZone<span class="token punctuation">,</span> zoneSpec<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>这便是 zone.js 中生命周期钩子的实现。有了这些钩子，我们可以做很多其他有用的事情，例如分析、记录和限制函数的执行和调用。</p> <h2 id="总结"><a href="#总结" class="header-anchor">#</a> 总结</h2> <p>本文我们主要介绍了 zone.js，它被设计用于解决异步编程中的执行上下文问题。</p> <p>在 zone.js 中，当前区域是随所有异步操作一起传播的异步上下文，可比作 Javascript 中的<code>this</code>。通过<code>zone.fork</code>可以创建子区域，通过<code>zone.run</code>可让函数（包括函数里的异步逻辑）在指定的区域中运行。</p> <p>zone.js 提供了丰富的生命周期钩子，可以使用 zone.js 的区域能力以及生命周期钩子解决前面我们提到的这些问题：</p> <ul><li>异步代码执行时，上下文发生了变更，导致预期不一致：使用 Zone 来执行相关代码</li> <li><code>throw Error</code>时，无法准确定位到上下文：使用生命周期钩子<code>onHandleError</code>进行处理和跟踪</li> <li>测试某个函数的执行耗时，但因为函数内有异步逻辑，无法得到准确的执行时间：使用生命周期钩子配合可得到具体的耗时</li></ul> <h3 id="参考"><a href="#参考" class="header-anchor">#</a> 参考</h3> <ul><li><a href="https://medium.com/ngconf/deep-dive-into-zone-js-part-1-execution-context-92166bbb957" target="_blank" rel="noopener noreferrer">Deep dive into Zone.js [Part 1: Execution Context]<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://medium.com/ngconf/deep-dive-into-zone-js-part-2-lifecycle-hooks-169da568227e" target="_blank" rel="noopener noreferrer">Deep dive into Zone.js [Part 2: LifeCycle Hooks]<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://indepth.dev/posts/1135/i-reverse-engineered-zones-zone-js-and-here-is-what-ive-found" target="_blank" rel="noopener noreferrer">I reverse-engineered Zones (zone.js) and here is what I’ve found<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/front-end-playground/edit/sourcecode/docs/front-end-others/deep-into-angular/angular-design-zonejs.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <blockquote>部分文章中使用了一些网站的截图，如果涉及侵权，请告诉我删一下谢谢~</blockquote> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</div> <div>出处：被删的前端游乐场</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-dom-define.html" class="prev">
          2.视图抽象定义
        </a></span> <span class="next"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html">
          4.Zone区域之ngZone
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-7375" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty2.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/front-end-playground/assets/js/app.1e2670bf.js" defer></script><script src="/front-end-playground/assets/js/2.38d016d1.js" defer></script><script src="/front-end-playground/assets/js/3.e3f029cb.js" defer></script><script src="/front-end-playground/assets/js/113.a2fd3964.js" defer></script>
  </body>
</html>
